library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

package our_types is

  type fifo_status is record
    level: natural;
    empty, almost_full, full : std_logic;
  end record;

end package our_types;

--------------------------------------------------------------------------------

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.our_types.all;

entity fifo is
  generic(width  : positive;
          length : positive);

  port( clk          : in  std_logic;
        reset        : in  std_logic;

        write_enable : in  std_logic;
        write_data   : in  unsigned(width-1 downto 0);
        write_error  : out std_logic;

        read_enable  : in  std_logic;
        read_data    : out unsigned(width-1 downto 0);
        read_error   : out std_logic;

        status       : out fifo_status);
end entity fifo;

architecture behavioral of fifo is

  type memory_type is array (length-1 downto 0) of unsigned (width-1 downto 0);

  type reg_type is record
       memory : memory_type;
       first : natural; -- read from first
       last : natural;  -- write at last
	   status: fifo_status;
  end record reg_type;

  signal fifo_previous, fifo_next: reg_type := ((others => (others => '0')),0,0,(0,'0','0','0'));

begin

  combinational_block : process(fifo_previous, write_enable, write_data, read_enable, reset) is
  variable fifo_temp : reg_type;
  variable last_write_enable,write_enabled,last_read_enable,read_enabled : std_logic := '0';
  begin
    
	fifo_temp := fifo_previous;
	
	
	if fifo_temp.status.level = 0 then
		fifo_temp.status.empty := '1';
		fifo_temp.status.almost_full := '0';
		fifo_temp.status.full := '0';
	elsif fifo_temp.status.level >= length-2 then
		fifo_temp.status.empty := '0';
		fifo_temp.status.almost_full := '1';
		if fifo_temp.status.level = length then
			fifo_temp.status.full := '1';
		elsif fifo_temp.status.level > length then
			fifo_temp := ((others => (others => '0')),0,0,(0,'1','0','0'));
		else
			fifo_temp.status.full := '0';
		end if;
	else
		fifo_temp.status.full := '0';
		fifo_temp.status.empty := '0';
		fifo_temp.status.almost_full := '0';
	end if;
	
	
	if false then
	if write_enable = '1' and last_write_enable = '0' then
        write_enabled := '1';
	else
		write_enabled := '0';
    end if;
      last_write_enable := write_enable;
	  
	if read_enable = '1' and last_read_enable = '0' then
        read_enabled := '1';
	else
		read_enabled := '0';
    end if;
      last_write_enable := read_enable;
	end if;
	
	if reset = '1' then
		write_error <= '0';
		read_error <= '0';
		read_data <= (others => '0');
		--status.level <= 0;
		--status.empty <= '1';
		--status.almost_full <= '0';
		--status.full <= '0';
		fifo_temp := ((others => (others => '0')),0,0,(0,'1','0','0'));
		
	elsif write_enable = '1' then
		read_data <= (others => '0');
		read_error <= '0';
		if fifo_temp.status.full = '1' then
			write_error <= '1';
		else
			fifo_temp.memory(fifo_temp.last) := write_data;
			fifo_temp.last := (fifo_temp.last +1) mod length;
			fifo_temp.status.level := fifo_temp.status.level + 1;
			write_error <= '0';
		end if;
	
	elsif read_enable = '1' then
		write_error <= '0';
		if fifo_temp.status.empty = '1' then
			read_error <= '1';
			read_data <= (others => '0');
		else
			read_data <= fifo_temp.memory(fifo_previous.first);
			fifo_temp.first := (fifo_temp.first +1) mod length;
			fifo_temp.status.level := fifo_temp.status.level - 1;
			read_error <= '0';
		end if;
	else
		read_data <= (others => '0');
		read_error <= '0';
		write_error <= '0';
	end if;

	status <= fifo_temp.status;
	fifo_next <= fifo_temp;
		
    -- *** Important recommendation:
    --   This process should only read the signals that are listed in the
    --   sensitivity list, and it should only assign to rIn and the output
    --   signals (and perhaps local variables).
  end process combinational_block;
  
  sequential_block : process(clk,reset) is
  begin
    if reset = '1' then
      fifo_previous.memory <= (others => (others => '0'));
      fifo_previous.first  <= 0;
      fifo_previous.last <= 0;
	  fifo_previous.status.level <= 0;
	  fifo_previous.status.empty <= '1';
	  fifo_previous.status.almost_full <= '0';
	  fifo_previous.status.full <= '0';
    
	elsif rising_edge(clk) then
      fifo_previous <= fifo_next;
    end if;

	end process sequential_block;

end architecture behavioral;
-----------------------------------------------------------------------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;

entity synchronous_posedges is
  port( i, clk : in std_logic;
        o : out std_logic := '0' );
end entity synchronous_posedges;

architecture behavioral of synchronous_posedges is
begin
  spe_beh : process(clk) is
    variable last_i : std_logic := '0';
  begin
    if rising_edge(clk) then
      if i = '1' and last_i = '0' then
        o <= '1';
      else
        o <= '0';
      end if;
      last_i := i;
    end if;
  end process spe_beh;
end architecture behavioral;
--------------------------------------------------------------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.our_types.all;

entity testbench_fifo is
end entity testbench_fifo;

architecture behavioral of testbench_fifo is

	signal clk,reset,write_enable,read_enable,sync_write,sync_read : std_logic;
	signal write_data : unsigned(3 downto 0);
	signal write_error,read_error : std_logic;
	signal read_data: unsigned(3 downto 0);
	signal status_display : fifo_status;

begin

	sync_posedge_write : entity work.synchronous_posedges(behavioral) port map(write_enable, clk, sync_write);
	sync_posedge_read : entity work.synchronous_posedges(behavioral) port map(read_enable, clk, sync_read);
	test_fifo : entity work.fifo(behavioral) generic map(4,4) port map(clk, reset, sync_write, write_data, write_error, sync_read, read_data, read_error, status_display);
	
	clock_gen: process is
	begin
		clk <= '1';
		wait for 5 us;
		clk <= '0';
		wait for 5 us;
	end process clock_gen;
	
	test_sequence: process  is
	variable test_done : std_logic := '0';
	begin
	
	--while test_done = '0' loop
		reset <= '0';
		wait for 5 us;
		reset <= '1';
		wait for 5 us;
		assert read_error = '0' and read_data = "0000" and write_error = '0' and status_display.level = 0 and status_display.empty = '1' and status_display.almost_full = '0' and status_display.full = '0'
		report "error in reset "
		severity failure;		
		reset<= '0';
		--if false then
		--empty read test
		read_enable <= '0','1' after 10 us;
		wait for 15 us;
		assert read_error = '1' and read_data = "0000"
		report "empty read test fail"
		severity failure;
		read_enable <= '0';
		wait for 5 us;
		
		--one write and read
		write_enable <= '0','1' after 10 us;
		write_data <= "0011";
		wait for 15 us;
		assert write_error = '0' and read_data = "0000"
		report "one write and read: write failure"
		severity failure;
		write_enable <= '0';
		wait for 5 us;
		read_enable <= '0','1' after 10 us;
		wait for 15 us;
		assert read_error = '0' and read_data = "0011"
		report "one write and read: read failure"
		severity failure;
		read_enable <= '0';
		wait for 5 us;
		
		-- five writes
		for i in 0 to 3 loop
			write_enable <= '0','1' after 10 us;
			write_data <= to_unsigned(i+1,4);
			wait for 15 us;
			assert write_error = '0' and read_data = "0000"
			report "error in iteration "
			severity failure;
			write_enable <= '0';
			wait for 5 us;
		end loop;
		write_enable <= '0','1' after 10 us;
		write_data <= "1111";
		wait for 15 us;
		assert write_error = '1' and read_data = "0000"
		report "error in fifth write "
		severity failure;
		write_enable <= '0';
		wait for 5 us;
		
		-- five reads
		for i in 0 to 3 loop
			read_enable <= '0','1' after 10 us;
			wait for 15 us;
			assert read_error = '0'
			report "error in iteration "
			severity failure;
			read_enable <= '0';
			wait for 5 us;
		end loop;
		read_enable <= '0','1' after 10 us;
			wait for 15 us;
			assert read_error = '1'
			report "error in fifth read "
			severity failure;
			read_enable <= '0';
			wait for 5 us;
		
		--reset
		reset <= '1';
		wait for 5 us;
		assert read_error = '0' and read_data = "0000" and write_error = '0' and status_display.level = 0 and status_display.empty = '1' and status_display.almost_full = '0' and status_display.full = '0'
		report "error in reset "
		severity failure;		
		reset<= '0';
		
			
		test_done := '1';
		
	
	--end loop;	
	end process test_sequence;
end architecture behavioral;
